package de.umr.fb12.vs.os.uebung;

import static de.umr.fb12.vs.os.Task.STATES.BLOCKED;
import static de.umr.fb12.vs.os.Task.STATES.DEAD;

import java.util.HashMap;
import java.util.TreeSet;

import de.umr.fb12.vs.os.CoreScheduler;
import de.umr.fb12.vs.os.SchedClass;
import de.umr.fb12.vs.os.Task;
// Michael Reif, Simon Martin, Leonid Glanz
public class SJFirstSched implements SchedClass {

	// this data structure sorts the tasks by the smallest pid and 
	// the shortest predicted value
	TreeSet<TimeAssignment> run_q = new TreeSet<TimeAssignment>();
	
	// in this structure we have a mapping from pid to the time assignment to
	// the task of this pid
	HashMap<Integer, TimeAssignment> map = new HashMap<Integer, TimeAssignment>();
	// the start of the current task
	long start_time = System.currentTimeMillis();
	// start of the last task
	long lastStart_time = 0;

	private class TimeAssignment implements Comparable<Object> {
		// like described in the slides that's the Tn 
		private Double lastExecutionTime = 0d;
		// like described in the slides that's the Sn
		private Double lastPredictedValue = 0d;
		// like described in the slides that's the alpha
		// we have selected the 0.8 variant, because it is
		// the nearest to the original values
		private Double alpha = 0.8d;


		public Double getLastPredictedValue() {
			return lastPredictedValue;
		}

		public int getTaskId() {
			return task.getPid();
		}

		public Task getTask() {
			return task;
		}

		private Task task = null;

		TimeAssignment(Task task) {
			this.task = task;

		}

		// the calculation form the slides alpha*Tn+(1-alpha)*Sn
		public void calculateNewValues(double executionTime) {
			this.lastPredictedValue = (this.alpha * lastExecutionTime)
					+ (1d - this.alpha) * this.lastPredictedValue;
			this.lastExecutionTime = executionTime;
		}

		@Override
		public boolean equals(Object arg0) {
			TimeAssignment ta = (TimeAssignment) arg0;
			return ta.getTaskId() == this.getTaskId();
		}

		@Override
		public int hashCode() {
			return getTaskId();
		}

		@Override
		public int compareTo(Object arg0) {
			TimeAssignment ta = (TimeAssignment) arg0;
			if (this.lastPredictedValue.compareTo(ta.getLastPredictedValue()) != 0) {
				return this.lastPredictedValue.compareTo(ta
						.getLastPredictedValue());
			}
			return new Integer(this.getTaskId()).compareTo(ta.getTaskId());
		}
	}

	// every time we put the time assignments into the run queue
	// instead of the tasks
	@Override
	public void enqueue_task(Task curr, Task enq) {
		if (curr.isIdle_task())
			curr.set_tsk_need_resched();
		TimeAssignment ta = getTimeAssignment(enq);
		run_q.add(ta);

	}
	
	// if a task was taken from the cpu, we calculate the predicted Value for this
	// task
	@Override
	public void dequeue_task(Task curr, Task deq) {
		if (curr == deq)
			curr.set_tsk_need_resched();
		TimeAssignment ta = getTimeAssignment(deq);
		ta.calculateNewValues((double) (System.currentTimeMillis() - lastStart_time));
		run_q.remove(ta);
	}

	@Override
	public void yield_task(Task curr, Task t) {
		if (curr == t)
			t.set_tsk_need_resched();
	}

	// the next task is the minimum of the TreeSet 
	@Override
	public Task pick_next_task() {
		// 
		Task next = null;
		if (run_q.size() > 0) {
			if (start_time != 0)
				lastStart_time = start_time;
			else
				lastStart_time = System.currentTimeMillis();
			start_time = System.currentTimeMillis();
			TimeAssignment ta=run_q.pollFirst();
			next = ta.getTask();
		}
		return next;
	}

	// that's a helping method for assigning to the map
	public TimeAssignment getTimeAssignment(Task task) {
		TimeAssignment ta = map.get(task.getPid());
		if (ta == null) {
			ta = new TimeAssignment(task);
			map.put(task.getPid(), ta);
		}
		return ta;
	}

	// if a task is not blocked and not dead, the task is running
	@Override
	public void put_prev_task(Task prev) {
		if (prev.getState() != BLOCKED && prev.getState() != DEAD) {
			TimeAssignment ta = getTimeAssignment(prev);
			ta.calculateNewValues((double) (System.currentTimeMillis() - lastStart_time));
			run_q.add(ta);
		}
	}

	// if a task is blocked or dead the task should be rescheduled
	@Override
	public void task_tick(Task curr) {
		if (curr.getSchedclass() == this
				&& (curr.getState() == BLOCKED || curr.getState() == DEAD))
			curr.set_tsk_need_resched();
	}

	// a new task is assigned to a time assignment object
	@Override
	public void task_new(Task curr, Task newtsk) {
		TimeAssignment ta = getTimeAssignment(newtsk);
		run_q.add(ta);
	}

	public static void main(String[] args) {
		SJFirstSched sjFirstSched = new SJFirstSched();
		CoreScheduler
				.init(sjFirstSched, "asm/simple_init.asm", "asm/dummy.asm");
	}
}
